package estructuras;

import java.util.*;
import java.util.Map.Entry;

import estructuras.common.Almacen;
import estructuras.common.Distribucion;
import estructuras.common.Fecha;
import estructuras.common.Paquete;


public class DistribucionDiscretaConexion implements Distribucion{
	@SuppressWarnings("unchecked")
	Vector<Integer>[][] frecuencias = (Vector<Integer>[][]) new Vector[7][24];
	int[][] total = new int[7][24];
	double[][] areaTotal2;
	double[][][] areaTotalAcumulado;

	class Entero{
		int val = 0;
		public Entero(int x){val = x;}
		public Entero(){new Entero(0);}
		public void incrementar(){val++;}
	}
	//uniforme entre 0 y maximoUniforme inclusive
	public DistribucionDiscretaConexion(Almacen origen, Almacen destino, int maximoUniforme){
		for(int i = 0; i < 7; i++) for(int j = 0; j < 24; j++){
			frecuencias[i][j] = new Vector<Integer>();
			for(int k = 0; k < maximoUniforme + 1; k++) frecuencias[i][j].add(1);
			frecuencias[i][j].add(0);
			total[i][j] = maximoUniforme + 1;
		}
		preprocess();		
	}
	
	public DistribucionDiscretaConexion(Almacen origen, Almacen destino, Fecha limite, Fecha creacionAlmacen){
		for(int i = 0; i < 7; i++) for(int j = 0; j < 24; j++){
			frecuencias[i][j] = new Vector<Integer>();
			frecuencias[i][j].add(0);
			total[i][j] = 0;
		}
		TreeMap<Fecha, Entero> contadores = new TreeMap<Fecha, Entero>();
		List<Paquete> paquetes = origen.getPaquetesOriginadosEnAlmacenHacia(destino);
		for(Paquete p : paquetes)
			if(p.getFechaRegistro().compareTo(limite) <= 0){
				if(!contadores.containsKey(p.getFechaRegistro())) contadores.put(p.getFechaRegistro(), new Entero(1));
				else contadores.get(p.getFechaRegistro()).incrementar();
			}
		Iterator<Entry<Fecha, Entero>> it = contadores.entrySet().iterator();
		while(it.hasNext()){
			Entry<Fecha, Entero> entry = it.next();
			Vector<Integer> frec = frecuencias[entry.getKey().getDia() % 7][entry.getKey().getHora()];
			if(frec.size() <= entry.getValue().val){
				frec.ensureCapacity(entry.getValue().val + 2);
				while(frec.size() <= entry.getValue().val + 1) frec.add(0);
			}
			frec.set(entry.getValue().val, frec.get(entry.getValue().val) + 1);
			total[entry.getKey().getDiaSemana()][entry.getKey().getHora()] += entry.getValue().val;
		}
		
		for(int diaSemana = 0; diaSemana < 7; diaSemana++)
			for(int hora = 0; hora < 24; hora++){
				int existieronHastaAhora = limite.getDia() / 7 + 1;
				int existieronHastaAntesCreacion = (creacionAlmacen.getDia() == 0 ? -1 : (creacionAlmacen.getDia() - 1) / 7) + 1;
				int existieron = existieronHastaAhora - existieronHastaAntesCreacion;
				if(limite.getDiaSemana() == diaSemana && limite.getHora() < hora) existieron--;
				if(creacionAlmacen.getDiaSemana() == diaSemana && creacionAlmacen.getHora() >= hora) existieron++;
				int ceros = existieron - total[diaSemana][hora];				
				frecuencias[diaSemana][hora].set(0, ceros);
			}
		
		preprocess();
	}
	void preprocess(){
		areaTotal2 = new double[7][24];
		areaTotalAcumulado = new double[7][24][];
		for(int diaSemana = 0; diaSemana < 7; diaSemana++)
			for(int hora = 0; hora < 24; hora++)
				areaTotalAcumulado[diaSemana][hora] = new double[frecuencias[diaSemana][hora].size()];
		
		for(int diaSemana = 0; diaSemana < 7; diaSemana++)
			for(int hora = 0; hora < 24; hora++)
				preprocess(diaSemana, hora);
	}
	void preprocess(int diaSemana, int hora){
		areaTotal2[diaSemana][hora] = 0.0;
		for(int x = 1; x < frecuencias[diaSemana][hora].size(); x++){
			areaTotal2[diaSemana][hora] += frecuencias[diaSemana][hora].get(x - 1).intValue() +
									 frecuencias[diaSemana][hora].get(x).intValue();
			areaTotalAcumulado[diaSemana][hora][x] = areaTotal2[diaSemana][hora];
		}
	}
	
	@Override
	public int getRandom(int diaSemana, int hora){
		double randomAcumulado = Math.random() * areaTotal2[diaSemana][hora];
		int le = 0, ri = areaTotalAcumulado[diaSemana][hora].length - 1;
		while(le < ri){
			int me = (le + ri) / 2;
			if(areaTotalAcumulado[diaSemana][hora][me] < randomAcumulado) le = me + 1;
			else ri = me;
		}
		return le;
	}
}
